
#include <png.h>

#include "tp_typedef.h"
#include "tp_image_png_decoder.h"
#include "tp_memory.h"

#include "tp_log.h"

static void* png_mem_alloc(png_structp png_ptr, png_alloc_size_t size)
{
    return tp_alloc((int)size);
}

static void png_mem_free(png_structp png_ptr, png_voidp ptr)
{
    tp_free((void*)ptr);

    return;
}

/*
 * We simply check the extension of the given file to see whether it's a png image or not.
 */
static TpRet tp_image_png_decoder_match(TpImageDecoder* thiz, const char* file)
{
    if(NULL == file) return TP_RET_FAIL;

    return (strstr(file, ".png") != NULL ? TP_RET_OK : TP_RET_FAIL);
}

static TpBitmap* tp_image_png_decoder_decode(TpImageDecoder* thiz, const char* file)
{
    int i = 0, j = 0;
    TpBitmap* bitmap = NULL;
    TpColor color = {0};
    TpColor* dest = NULL;
    FILE* fp = NULL;

    png_structp png_ptr = NULL;
    png_infop info_ptr = NULL;

    png_uint_32 w = 0, h = 0;
    png_byte color_type = 0;
    png_size_t row_bytes = 0;
    png_bytep* png_rows_ptr = NULL;
    png_bytep src = NULL;

    fp = fopen(file, "rb");
    if(NULL == fp)
    {
       return NULL;
    }

    png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL, NULL, png_mem_alloc, png_mem_free);
    /*png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);*/
    if(NULL == png_ptr)
    {
        fclose(fp);
        return NULL;
    }

    #ifdef PNG_SETJMP_SUPPORTED
    if(setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }
    #endif

    info_ptr = png_create_info_struct(png_ptr);
    if(NULL == info_ptr)
    {
        fclose(fp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }

    png_init_io(png_ptr, fp);

    #ifdef PNG_SETJMP_SUPPORTED
    if(setjmp(png_jmpbuf(png_ptr)))
    {
        fclose(fp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }
    #endif

    png_read_info(png_ptr, info_ptr);

    w = png_get_image_width(png_ptr, info_ptr);
    h = png_get_image_height(png_ptr, info_ptr);
    color_type = png_get_color_type(png_ptr, info_ptr);
    row_bytes = png_get_rowbytes(png_ptr, info_ptr);

    /*
     * Currently we ony support png image in RGB or RGBA color format.
     */
    if(PNG_COLOR_TYPE_RGB != color_type && PNG_COLOR_TYPE_RGBA != color_type)
    {
        tp_loge("%s: the color type %d of the given image is not supported.\nCurrently only image in 24/32 bits are supported.", __FUNCTION__, color_type);

        fclose(fp);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
        return NULL;
    }

    png_rows_ptr = (png_bytepp)tp_alloc(sizeof(png_bytep) * h);
    for(i = 0; i < h; ++i)
    {
        png_rows_ptr[i] = (png_bytep)tp_alloc(row_bytes);
    }

    /*
     * We don't need to call png_set_interlace_handling() 
     * as png_read_image() will do this job itself.
     */
    png_read_image(png_ptr, png_rows_ptr);
    bitmap = tp_bitmap_create(w, h, &color);
    dest = tp_bitmap_bits(bitmap);

    /*
     * Now we will analyze the image data.
     */
    if(PNG_COLOR_TYPE_RGB == color_type)
    {
        for(i = 0; i < h; ++i)
        {
            src = png_rows_ptr[i];
            for(j = 0; j < w; ++j)
            {
                dest->r = src[0];
                dest->g = src[1];
                dest->b = src[2];
                dest->a = 0xff;

                dest++;
                src += 3;
            }
        }
    }
    else if(PNG_COLOR_TYPE_RGBA == color_type)
    {
        for(i = 0; i < h; ++i)
        {
            src = png_rows_ptr[i];
            for(j = 0; j < w; ++j)
            {
                /* we won't copy the pixel bytes unless it's alpha value is valid. */
                if(0 != src[3])
                {
                    dest->r = src[0];
                    dest->g = src[1];
                    dest->b = src[2];
                }
                dest->a = src[3];
                dest++;
                src += 4;
            }
        }
    }

    for(i = 0; i < h; ++i)
    {
        tp_free(png_rows_ptr[i]);
    }
    tp_free(png_rows_ptr);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);
    fclose(fp);

    return bitmap;
}

static void tp_image_png_decoder_destroy(TpImageDecoder* thiz)
{
    if(NULL != thiz)
    {
        tp_free(thiz);
    }

    return;
}

TpImageDecoder* tp_image_png_decoder_create(void)
{
    TpImageDecoder* thiz= (TpImageDecoder*)tp_alloc(sizeof(TpImageDecoder));
    if(NULL != thiz)
    {
        thiz->match = tp_image_png_decoder_match;
        thiz->decode = tp_image_png_decoder_decode;
        thiz->destroy = tp_image_png_decoder_destroy;
    }

    return thiz;
}

